package camid.distribution.activeobject;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.broker.Requestor;
import camid.distribution.protocol.Endpoint;
import camid.distribution.protocol.InvocationDescriptor;
import camid.services.nameservice.ObjectReference;
import camid.services.nodemanager.LookupInterceptor;

public class RemoteInvocationHandler implements InvocationHandler {

	private String sessionId;
	
	private String objectId;
	
	private Class<?> entityClass;
	
	private Requestor requestor;
	
	private Endpoint endpoint;
	
	private ExecutorService executor;
	
	private Map<String, Object> invocationContext;
	
	private static final Logger logger = LogManager.getLogger(RemoteInvocationHandler.class.getName());
	
	public RemoteInvocationHandler(String sessionId, String objectId, Class<?> entityClass, Requestor requestor, Endpoint endpoint) {
		this.sessionId = sessionId;
		this.objectId = objectId;
		this.entityClass = entityClass;
		this.endpoint = endpoint;
		this.requestor = requestor;
		this.executor = ProxyExecutorManager.getInstance().getExecutor(objectId);
		this.invocationContext = new TreeMap<String,Object>();
	}

	public RemoteInvocationHandler(String sessionId, Class<?> entityClass,
			Requestor requestor, ObjectReference reference) {
		this.sessionId = sessionId;
		this.objectId = reference.getObjectId();
		this.entityClass = entityClass;
		this.endpoint = reference.getEndpoint();
		this.requestor = requestor;
		this.executor = ProxyExecutorManager.getInstance().getExecutor(objectId);
		this.invocationContext = new TreeMap<String,Object>();
		this.invocationContext.put(LookupInterceptor.FUTURE_REQUEST, reference.getNextRelookup());
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] params)
			throws Throwable 
	{
		final InvocationDescriptor invocationDescriptor = new InvocationDescriptor(
				sessionId, objectId, entityClass.getCanonicalName(), 
				method.getName(), params);
		
		if(endpoint != null)
		{
			logger.info("Requesting camid://" + endpoint.getHostname() + ":" + endpoint.getPort() 
				+ "/" + entityClass.getName() + "/" + objectId + "/" + method.getName());
		}
		
		Future<?> future = this.executor.submit(new Callable<Object> () {
			@Override
			public Object call() throws Exception {
				Object response = requestor.request(invocationContext, endpoint, invocationDescriptor);
				if(response instanceof AsyncResult<?>)
				{
					AsyncResult<?> asyncResult = (AsyncResult<?>) response;
					return asyncResult.get();
				}
				return response;
			}
		});
		
		if(method.getReturnType().isAssignableFrom(Future.class))
		{
			return future;
		}
		
		return future.get();
	}
	
	public void shutdownExecutor()
	{
		this.executor.shutdown();
	}
	
	
	public String getObjectId() {
		return objectId;
	}

}
